msg_tool\scripts\emote/
pimg.rs

1//! Emote Multiple Image File (.pimg)
2use crate::ext::io::*;
3use crate::ext::psb::*;
4use crate::scripts::base::*;
5use crate::try_option;
6use crate::types::*;
7use crate::utils::img::*;
8use anyhow::Result;
9use emote_psb::PsbReader;
10use libtlg_rs::*;
11use std::collections::HashMap;
12use std::io::{Read, Seek};
13use std::path::Path;
14
15#[derive(Debug)]
16/// Emote PImg Script Builder
17pub struct PImgBuilder {}
18
19impl PImgBuilder {
20    /// Creates a new instance of `PImgBuilder`
21    pub const fn new() -> Self {
22        Self {}
23    }
24}
25
26impl ScriptBuilder for PImgBuilder {
27    fn default_encoding(&self) -> Encoding {
28        Encoding::Utf8
29    }
30
31    fn build_script(
32        &self,
33        buf: Vec<u8>,
34        filename: &str,
35        _encoding: Encoding,
36        _archive_encoding: Encoding,
37        config: &ExtraConfig,
38        _archive: Option<&Box<dyn Script>>,
39    ) -> Result<Box<dyn Script>> {
40        Ok(Box::new(PImg::new(MemReader::new(buf), filename, config)?))
41    }
42
43    fn build_script_from_file(
44        &self,
45        filename: &str,
46        _encoding: Encoding,
47        _archive_encoding: Encoding,
48        config: &ExtraConfig,
49        _archive: Option<&Box<dyn Script>>,
50    ) -> Result<Box<dyn Script>> {
51        if filename == "-" {
52            let data = crate::utils::files::read_file(filename)?;
53            Ok(Box::new(PImg::new(MemReader::new(data), filename, config)?))
54        } else {
55            let f = std::fs::File::open(filename)?;
56            let reader = std::io::BufReader::new(f);
57            Ok(Box::new(PImg::new(reader, filename, config)?))
58        }
59    }
60
61    fn build_script_from_reader(
62        &self,
63        reader: Box<dyn ReadSeek>,
64        filename: &str,
65        _encoding: Encoding,
66        _archive_encoding: Encoding,
67        config: &ExtraConfig,
68        _archive: Option<&Box<dyn Script>>,
69    ) -> Result<Box<dyn Script>> {
70        Ok(Box::new(PImg::new(reader, filename, config)?))
71    }
72
73    fn extensions(&self) -> &'static [&'static str] {
74        &["pimg"]
75    }
76
77    fn script_type(&self) -> &'static ScriptType {
78        &ScriptType::EmotePimg
79    }
80
81    fn is_this_format(&self, filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
82        if Path::new(filename)
83            .extension()
84            .map(|ext| ext.to_ascii_lowercase() == "pimg")
85            .unwrap_or(false)
86            && buf_len >= 4
87            && buf.starts_with(b"PSB\0")
88        {
89            return Some(255);
90        }
91        None
92    }
93
94    fn is_image(&self) -> bool {
95        true
96    }
97}
98
99#[derive(Debug)]
100/// Emote PImg Script
101pub struct PImg {
102    psb: VirtualPsbFixed,
103    overlay: Option<bool>,
104}
105
106impl PImg {
107    /// Create a new PImg script
108    ///
109    /// * `reader` - The reader containing the PImg script data
110    /// * `filename` - The name of the file
111    /// * `config` - Extra configuration options
112    pub fn new<R: Read + Seek>(reader: R, filename: &str, config: &ExtraConfig) -> Result<Self> {
113        let mut psb = PsbReader::open_psb(reader)
114            .map_err(|e| anyhow::anyhow!("Failed to open PSB from {}: {:?}", filename, e))?;
115        let psb = psb
116            .load()
117            .map_err(|e| anyhow::anyhow!("Failed to load PSB from {}: {:?}", filename, e))?
118            .to_psb_fixed();
119        Ok(Self {
120            psb,
121            overlay: config.emote_pimg_overlay,
122        })
123    }
124
125    fn load_img(&self, layer_id: i64) -> Result<Tlg> {
126        let layer_id = layer_id as usize;
127        let psb = self.psb.root();
128        let reference = &psb[format!("{layer_id}.tlg")];
129        let resource_id = reference
130            .resource_id()
131            .ok_or_else(|| anyhow::anyhow!("Layer {layer_id} does not have a resource ID"))?
132            as usize;
133        if resource_id >= self.psb.resources().len() {
134            return Err(anyhow::anyhow!(
135                "Resource ID {resource_id} for layer {layer_id} is out of bounds"
136            ));
137        }
138        let resource = &self.psb.resources()[resource_id];
139        Ok(load_tlg(MemReaderRef::new(&resource))?)
140    }
141}
142
143impl Script for PImg {
144    fn default_output_script_type(&self) -> OutputScriptType {
145        OutputScriptType::Json
146    }
147
148    fn default_format_type(&self) -> FormatOptions {
149        FormatOptions::None
150    }
151
152    fn is_image(&self) -> bool {
153        true
154    }
155
156    fn is_multi_image(&self) -> bool {
157        true
158    }
159
160    fn export_multi_image<'a>(
161        &'a self,
162    ) -> Result<Box<dyn Iterator<Item = Result<ImageDataWithName>> + 'a>> {
163        let psb = self.psb.root();
164        let overlay = self.overlay.unwrap_or_else(|| {
165            psb["layers"]
166                .members()
167                .all(|layer| layer["group_layer_id"].is_none())
168        });
169        if !overlay {
170            return Ok(Box::new(PImgIter2 {
171                pimg: self,
172                layers: psb.iter(),
173            }));
174        }
175        let width = psb["width"]
176            .as_u32()
177            .ok_or(anyhow::anyhow!("missing width"))?;
178        let height = psb["height"]
179            .as_u32()
180            .ok_or(anyhow::anyhow!("missing height"))?;
181        if !psb["layers"].is_list() {
182            return Err(anyhow::anyhow!("layers is not a list"));
183        }
184        if psb["layers"].len() == 0 {
185            return Ok(Box::new(std::iter::empty()));
186        }
187        let mut bases = HashMap::new();
188        for i in psb["layers"].members() {
189            if !i["diff_id"].is_none() {
190                continue; // Skip layers with diff_id
191            }
192            let layer_id = i["layer_id"]
193                .as_i64()
194                .ok_or(anyhow::anyhow!("missing layer_id"))?;
195            let top = i["top"].as_u32().ok_or(anyhow::anyhow!("missing top"))?;
196            let left = i["left"].as_u32().ok_or(anyhow::anyhow!("missing left"))?;
197            let opacity = i["opacity"]
198                .as_u8()
199                .ok_or_else(|| anyhow::anyhow!("Layer does not have a valid opacity"))?;
200            bases.insert(layer_id, (self.load_img(layer_id)?, top, left, opacity));
201        }
202        Ok(Box::new(PImgIter {
203            pimg: self,
204            width,
205            height,
206            layers: psb["layers"].members(),
207            bases,
208        }))
209    }
210}
211
212struct PImgIter<'a> {
213    pimg: &'a PImg,
214    width: u32,
215    height: u32,
216    layers: ListIter<'a>,
217    bases: HashMap<i64, (Tlg, u32, u32, u8)>,
218}
219
220impl<'a> Iterator for PImgIter<'a> {
221    type Item = Result<ImageDataWithName>;
222
223    fn next(&mut self) -> Option<Self::Item> {
224        match self.layers.next() {
225            Some(layer) => {
226                let layer_id =
227                    try_option!(layer["layer_id"].as_i64().ok_or_else(|| {
228                        anyhow::anyhow!("Layer does not have a valid layer_id")
229                    }));
230                let layer_name = try_option!(
231                    layer["name"]
232                        .as_str()
233                        .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid name") })
234                );
235                let width = try_option!(
236                    layer["width"]
237                        .as_u32()
238                        .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid width") })
239                );
240                let height = try_option!(
241                    layer["height"]
242                        .as_u32()
243                        .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid height") })
244                );
245                let top = try_option!(
246                    layer["top"]
247                        .as_u32()
248                        .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid top") })
249                );
250                let left = try_option!(
251                    layer["left"]
252                        .as_u32()
253                        .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid left") })
254                );
255                let opacity = try_option!(
256                    layer["opacity"]
257                        .as_u8()
258                        .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid opacity") })
259                );
260                if layer["diff_id"].is_none() {
261                    let base = &try_option!(self.bases.get(&layer_id).ok_or(anyhow::anyhow!(
262                        "Base image for layer_id {} not found",
263                        layer_id
264                    )))
265                    .0;
266                    let mut data = ImageData {
267                        width: self.width,
268                        height: self.height,
269                        color_type: match base.color {
270                            TlgColorType::Bgr24 => ImageColorType::Bgr,
271                            TlgColorType::Bgra32 => ImageColorType::Bgra,
272                            TlgColorType::Grayscale8 => ImageColorType::Grayscale,
273                        },
274                        depth: 8,
275                        data: base.data.clone(),
276                    };
277                    if opacity != 255 {
278                        try_option!(apply_opacity(&mut data, opacity));
279                    }
280                    if self.width != width || self.height != height || top != 0 || left != 0 {
281                        data =
282                            try_option!(draw_on_canvas(data, self.width, self.height, left, top));
283                    }
284                    return Some(Ok(ImageDataWithName {
285                        name: layer_name.to_string(),
286                        data,
287                    }));
288                } else {
289                    let diff_id =
290                        try_option!(layer["diff_id"].as_i64().ok_or_else(|| {
291                            anyhow::anyhow!("Layer does not have a valid diff_id")
292                        }));
293                    let (base, base_top, base_left, base_opacity) = try_option!(
294                        self.bases
295                            .get(&diff_id)
296                            .ok_or(anyhow::anyhow!("Base image layer {} not found", diff_id))
297                    );
298                    let diff = try_option!(self.pimg.load_img(layer_id));
299                    if base.color != diff.color {
300                        return Some(Err(anyhow::anyhow!(
301                            "Color type mismatch for layer_id {}: base color {:?}, diff color {:?}",
302                            layer_id,
303                            base.color,
304                            diff.color
305                        )));
306                    }
307                    let mut base_img = ImageData {
308                        width: base.width,
309                        height: base.height,
310                        color_type: match base.color {
311                            TlgColorType::Bgr24 => ImageColorType::Bgr,
312                            TlgColorType::Bgra32 => ImageColorType::Bgra,
313                            TlgColorType::Grayscale8 => ImageColorType::Grayscale,
314                        },
315                        depth: 8,
316                        data: base.data.clone(),
317                    };
318                    if base.width != self.width
319                        || base.height != self.height
320                        || *base_top != 0
321                        || *base_left != 0
322                    {
323                        base_img = try_option!(draw_on_canvas(
324                            base_img,
325                            self.width,
326                            self.height,
327                            *base_left,
328                            *base_top
329                        ));
330                    }
331                    if *base_opacity != 255 {
332                        try_option!(apply_opacity(&mut base_img, *base_opacity));
333                    }
334                    let diff = ImageData {
335                        width: diff.width,
336                        height: diff.height,
337                        color_type: match diff.color {
338                            TlgColorType::Bgr24 => ImageColorType::Bgr,
339                            TlgColorType::Bgra32 => ImageColorType::Bgra,
340                            TlgColorType::Grayscale8 => ImageColorType::Grayscale,
341                        },
342                        depth: 8,
343                        data: diff.data.clone(),
344                    };
345                    try_option!(draw_on_img_with_opacity(
346                        &mut base_img,
347                        &diff,
348                        left,
349                        top,
350                        opacity
351                    ));
352                    Some(Ok(ImageDataWithName {
353                        name: layer_name.to_string(),
354                        data: base_img,
355                    }))
356                }
357            }
358            None => None,
359        }
360    }
361}
362
363struct PImgIter2<'a> {
364    pimg: &'a PImg,
365    layers: ObjectIter<'a>,
366}
367
368impl<'a> Iterator for PImgIter2<'a> {
369    type Item = Result<ImageDataWithName>;
370
371    fn next(&mut self) -> Option<Self::Item> {
372        match self.layers.next() {
373            Some((k, v)) => {
374                if !k.ends_with(".tlg") {
375                    return self.next();
376                }
377                let resource_id = try_option!(
378                    v.resource_id()
379                        .ok_or_else(|| anyhow::anyhow!("Layer {} does not have a resource ID", k))
380                ) as usize;
381                let name = k.trim_end_matches(".tlg").to_string();
382                if resource_id >= self.pimg.psb.resources().len() {
383                    return Some(Err(anyhow::anyhow!(
384                        "Resource ID {} for layer {} is out of bounds",
385                        resource_id,
386                        k
387                    )));
388                }
389                let resource = &self.pimg.psb.resources()[resource_id];
390                let tlg = try_option!(load_tlg(MemReaderRef::new(&resource)));
391                Some(Ok(ImageDataWithName {
392                    name,
393                    data: ImageData {
394                        width: tlg.width,
395                        height: tlg.height,
396                        color_type: match tlg.color {
397                            TlgColorType::Bgr24 => ImageColorType::Bgr,
398                            TlgColorType::Bgra32 => ImageColorType::Bgra,
399                            TlgColorType::Grayscale8 => ImageColorType::Grayscale,
400                        },
401                        depth: 8,
402                        data: tlg.data.clone(),
403                    },
404                }))
405            }
406            None => None,
407        }
408    }
409}